#include "XpmTermDoc.h"

namespace xpm {
	TermBlocksIter XpmTermDoc::GetTermBlock(int blockpos, TermBlocksIter start) {
		for(TermBlocksIter it = start; it != _termblocks.end(); ++it) {
			//for my evaluation the <= is very important
			if(blockpos == (*it)->position())
				return it;
		}
		return _termblocks.end();
	}

	TermBlocksIter XpmTermDoc::GetTermBlockBeforePos(int blockpos) {
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			if(blockpos == (*it)->position()) {
				if(it != _termblocks.begin()) {
					return --it;
				}else {
					return _termblocks.begin();
				}
			}
		}
		return _termblocks.end();
	}

	TermBlocksIter XpmTermDoc::GetTermBlockInvolvesPos(int pos) {
		if(pos > 0) {
			for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
				if((*it)->position() <= pos && (*it)->end() >= pos)
					return it;
			}
		}
		return _termblocks.end();
	}

	RemainRangePair XpmTermDoc::AddMap(int startpos, int blockpos, wstring text, conex::Term* term) {
		//int length = text.length();
		//for terms beginning insert at this function
		//RemainRangePair pair;
		TermBlocksIter nextIt = _termblocks.end();
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			//for my evaluation the <= is very important
			if( (*it)->position() == blockpos ) {
				return (*it)->AddMap(startpos, text, term);
			}else if((*it)->position() > blockpos) {
				nextIt = it;
				--nextIt;
			}
		}
		// not found need new block or nextIt is not the end
		XpmTermBlock* block = new XpmTermBlock(blockpos);
		//insert before the nextIt
		_termblocks.insert(nextIt, block); 	
		return block->AddMap(startpos, text, term);		
	}

	RemainRange XpmTermDoc::RemoveMap(int word_ix, int block_ix) {
		RemainRange range;
		TermBlocksIter block = _termblocks.end();
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			if((*it)->position() ==block_ix) {
				for(TermIter t = (*it)->GetTerms().begin(); t !=  (*it)->GetTerms().end(); ++t) {
					if(t->posInBlock() == word_ix - block_ix)
						return (*it)->RemoveMap(t);
				}
			}
		}		
		return range;
	}

	RemainRangePair XpmTermDoc::AddCharsInPosRange(int startpos, int charsAdded) {
		RemainRangePair pair;
		//process		
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			//we are also interest in the add chars which impact the whole block
			// case 3
			if ( startpos >= (*it)->end()) {
				continue;
			}			
			// case 1
			else if( startpos < (*it)->position()) {
				//(*it)->AddSomethingBefore(charsAdded);
				(*it)->setTextBlockPos((*it)->position() + charsAdded);
			}			
			// case 2
			else {
				pair = (*it)->AddCharsInPosRange(startpos, charsAdded);
			}
			
		}			
		//after insert other characters
		//after both insert, we need to end the edit
		// all will finished insert in this function.
		return pair;
	}

	void XpmTermDoc::AddSomethingBefore(int startpos, int charsAdded) {
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			//we are also interest in the add chars which impact the whole block
			// case 3
			 if( startpos <= (*it)->position()) {
				//(*it)->AddSomethingBefore(charsAdded);
				(*it)->setTextBlockPos((*it)->position() + charsAdded);
			}
		}
	}

	RemainRangePair XpmTermDoc::RemoveCharsInPosRange(int startpos, int charsRemoved) {
		RemainRangePair pair;
		int end = startpos + charsRemoved;
		//using the method of exception
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ) {
			//case 1
			if( end <= (*it)->position()) {
				(*it)->setTextBlockPos((*it)->position() - charsRemoved);
				//(*it)->RemoveSomethingBefore(charsRemoved);
				++it;
			}
			//case 3.1
			else if( end >= (*it)->end() && startpos <= (*it)->start()) {
				delete *it;
				*it = 0;
				it = _termblocks.erase(it);
			}
			//case 3.3
			else if( end >= (*it)->end() && startpos >= (*it)->end()) {
				++it;			
			}
			//case 2, 3.2
			else {
				pair = (*it)->RemoveCharsInPosRange(startpos, charsRemoved);				
				if((*it)->GetTerms().size() == 0) {
					delete *it;
					*it = 0;
					it = _termblocks.erase(it);
				}else {
					++it;
				}
			}			
		}	
		return pair;
	}

	void XpmTermDoc::RemoveSomethingBefore(int startpos, int charsRemoved) {
		int end = startpos + charsRemoved;
		//using the method of exception
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			//case 1
			if( end <= (*it)->position()) {
				(*it)->setTextBlockPos((*it)->position() - charsRemoved);
				//(*it)->RemoveSomethingBefore(charsRemoved);
			}
		}
	}

	void XpmTermDoc::PasteFirstBlock(int start, XpmTermBlock* docblock, XpmTermBlock* clipboardblock) {
		bool bFound = false;
		start -= docblock->position();
		TermIter it = docblock->GetTerms().begin();
		for(; it !=docblock->GetTerms().end(); ++it) {
			if( !bFound && start < it->posInBlock() )
				break;
			else if ( start >= it->posInBlock() && start <= it->end()) {
				it = docblock->GetTerms().erase(it);
				break;
			}
		}
		// insert terms from first block of clipboard
		for(TermIter tit = clipboardblock->GetTerms().begin(); tit != clipboardblock->GetTerms().end(); ++tit) {
			TermRange term(*tit);
			term.setPosInBlock(term.posInBlock() + start);
			it = docblock->GetTerms().insert(it, term);
			++it;
		}
	}

	//Add a list of blocks into the document
	void XpmTermDoc::InsertXpmBlocksFromClipboard(int blockpos, int start, XpmClipboard * clipboard) {
		// one turn for inserting
		TermBlocks blocks = clipboard->blocks();
		if(blocks.size() > 0) {
			TermBlocksIter it = _termblocks.begin();
			for(; it != _termblocks.end(); ++it) {
				if ( blockpos == (*it)->position()) {
					break;					
				}
			}
			if( it == _termblocks.end())
				it = _termblocks.insert(it, new XpmTermBlock(blockpos));
			// process first block					
			PasteFirstBlock(start, (*it), (*blocks.begin()));
			// process other blocks
			if( blocks.size() > 1) {						
				TermBlocksIter bit = blocks.begin();
				// start with 2rd element
				++bit;
				for(; bit != blocks.end(); ++bit) {
					++it;
					// insert new block
					XpmTermBlock* block = new XpmTermBlock(**bit);
					block->setTextBlockPos(block->position() + start);
					it = _termblocks.insert(it,block); 
				}
			}
		}
		
	}
	//get blocks from the selection
	void XpmTermDoc::GetXpmBlocksFromSelectionToClipboard(int start, int end, XpmClipboard * clipboard) {
		clipboard->setLength(end - start);
		clipboard->setPos(start);
		//clipboard.set
		TermBlocksIter startIter = _termblocks.end(), endIter = _termblocks.end();
		// one turn for coping
		bool bgetStart = false;

		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			// case 1
			if( end <= (*it)->position()) {
				return ;
			}
			// case 3.3
			else if( start >= (*it)->end()) 
				break;
			else {
				// create a new block
				int pos = (*it)->position() - start;
				pos = pos < 0 ? 0 : pos;
				XpmTermBlock * block = new XpmTermBlock(pos);
				// copy old block to new block
				// actiion
				CopyBlock(start, end, *it, block);
				if( block->GetTerms().empty()) {
					delete block;
					block = NULL;
				}else {
					clipboard->blocks().push_back(block);
				}
			}
		}
	}

	//copy old block to new block
	void XpmTermDoc::CopyBlock(int start, int end, XpmTermBlock* oldblock, XpmTermBlock* newblock) {
		start -= oldblock->position();
		start = start < 0 ? 0 : start;
		end -= oldblock->position();
		for(TermIter it = oldblock->GetTerms().begin(); it != oldblock->GetTerms().end(); ++it)  {
			// case 1
			if( end <= it->posInBlock()) {
				return ;
			}
			// case 3.3
			if( start >= it->end()) 
				break;
			// case 3.1
			if( end >= it->end() && start <= it->posInBlock()) {
				// add new term
				TermRange term(*it);
				term.setPosInBlock(it->posInBlock() + start);
				newblock->GetTerms().push_back(term);
			}
		}
	}

	//precise find three seleciton range
	/*void XpmTermDoc::FindByRange(SelectionRange* range, TermBlocksIter &block, TermIter &term) {
		block = _termblocks.end();
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			if((*it)->position() == range->blockpos) {
				block = it;
				(*it)->FindByRange(range, term);
			}
		}
	}*/

	// get total maps in doc
	int XpmTermDoc::totalMaps() {
		int total = 0;
		for(TermBlocksIter it = _termblocks.begin(); it != _termblocks.end(); ++it) {
			total += (*it)->GetTerms().size();
		}
		return total;
	}

	vector<TermRange> XpmTermDoc::findTermRanges(int pos, int len) const {
		vector<TermRange> trs;
		for(list<XpmTermBlock*>::const_iterator it = _termblocks.cbegin(); it != _termblocks.cend(); ++it) {
			if ( ! ( pos >= (*it)->end() || (*it)->position() >= (pos + len) )) {
				(*it)->findTermRanges(pos, len, trs);
			}
		}
		return trs;
	}
}